package com.scr1pt.extension

import com.scr1pt.extension.enum.EHashType
import java.io.*
import java.net.URLDecoder
import java.net.URLEncoder
import java.nio.charset.Charset
import java.nio.charset.StandardCharsets
import java.text.SimpleDateFormat
import java.util.*
import java.util.concurrent.TimeUnit
import kotlin.system.exitProcess

/**
 * 说明: 字符串扩展
 *
 * 创建者: tum0r
 *
 * 时间: 2021/8/18 17:10
 */

/**
 * 数据库 LIKE 语句添加占位符（默认%）
 *
 * [left] 是否添加左百分号
 * [right] 是否添加右百分号
 */
@JvmOverloads
fun String.fillLikeString(fillString: String = "%", left: Boolean = true, right: Boolean = true): String = (if (left) fillString else "") + this + (if (right) fillString else "")

/**
 * 对字符串进行遮挡，例如：tum0r -> t***r
 *
 * @param[start] 被遮挡的开始下标，大于等于0
 * @param[end] 被遮挡的结束下标，小于等于字符串长度，遮挡时不包括此下标
 * @param[maskCharacterLength] 遮挡字符的数量，默认为0，表示根据end和start进行计算
 * @param[maskCharacter] 遮挡字符，默认为'*'
 *
 * @return 遮挡后的字符串
 *
 * @throws[Exception] 起始位置小于0，结束位置超过字符串长度，起始位置大于等于结束位置
 */
@JvmOverloads
fun String.mask(start: Int, end: Int, maskCharacterLength: Int = 0, maskCharacter: Char = '*'): String {
    if (start < 0) throw RuntimeException("起始位置不能小于0")
    if (end > this.length) throw RuntimeException("结束位置不能超过字符串长度: " + this.length)
    if (start >= end) throw RuntimeException("起始位置不能大于或等于结束位置")

    val temp = this.toCharArray()
    val result = StringBuilder()
    for (i in 0 until start) result.append(temp[i])

    val maskCharacterString = maskCharacter.toString()
    if (maskCharacterLength == 0) result.append(maskCharacterString.repeat(end - start))
    else result.append(maskCharacterString.repeat(maskCharacterLength))

    for (i in end until this.length) result.append(temp[i])
    return result.toString()
}

/**
 * 字符串IP地址转成Int类型
 */
fun String.getIPInt(): Int {
    val temp = this.split(".")
    val result = IntArray(4)
    for (i in temp.indices) result[i] = temp[i].toInt()
    return result.getIPInt()
}

/**
 * 获取不带后缀的文件名
 */
fun String.fileNameWithoutExtension(): String = File(this).nameWithoutExtension

/**
 * 获取文件后缀名
 */
fun String.fileExtension(): String = File(this).extension

// region 文件检查
/**
 * 是否是文件（文件是否存在）
 *
 * @param[ignoreIsHidden] 是否忽略被检查的文件是不是隐藏的
 */
@JvmOverloads
fun String.isExistsFile(ignoreIsHidden: Boolean = false): Boolean = File(this).isExistsFile(ignoreIsHidden)

/**
 * 是否是文件夹（文件夹是否存在）
 *
 * @param[ignoreIsHidden] 是否忽略被检查的文件是不是隐藏的
 */
@JvmOverloads
fun String.isExistsFolder(ignoreIsHidden: Boolean = false): Boolean = File(this).isExistsFolder(ignoreIsHidden)

/**
 * 检查是否存在目录穿越
 *
 * @param[filePath] 被检查的文件路径
 * @return false为存在目录穿越
 */
fun String.checkDirThrough(filePath: String): Boolean = File(this).checkDirThrough(File(filePath))
// endregion

// region 操作文件/文件夹
/**
 * 创建文件夹
 */
fun String.createDir(): Boolean = File(this).createDir()

/**
 * 获取文件大小，文件夹则把其中所有文件大小加起来
 *
 * @param[ignoreIsHidden] 是否忽略是否是隐藏文件
 */
@JvmOverloads
fun String.getFileSize(ignoreIsHidden: Boolean = false): Long = File(this).getFileSize(ignoreIsHidden)

/**
 * 批量更改哔哩哔哩下载的视频名
 *
 * @param[regex] 匹配文件名的正则
 * @param[ignoreHidden] 是否忽略隐藏文件
 */
@JvmOverloads
fun String.biLiBiLi(regex: Regex = "( )(\\d+.*?)(\\))".toRegex(), ignoreHidden: Boolean = true) = File(this).biLiBiLi(regex, ignoreHidden)
// endregion

// region 日志

/**
 * 获取日志格式的字符串
 *
 * @param[prefix] 日志前缀
 * @param[suffix] 日志后缀
 */
@JvmOverloads
fun String.getLogString(prefix: String = "", suffix: String = ""): String = "$prefix$this$suffix"

/**
 * 输出日志到PrintStream
 *
 * @param[prefix] 日志前缀
 * @param[suffix] 日志后缀
 * @param[printStream] 日志输出流
 * @param[line] 日志是否换行
 */
private fun String.log(prefix: String, suffix: String, printStream: PrintStream, line: Boolean) = if (line) printStream.println(this.getLogString(prefix, suffix)) else printStream.print(this.getLogString(prefix, suffix))

/**
 * 输出普通级别日志到PrintStream
 *
 * @param[prefix] 日志前缀
 * @param[suffix] 日志后缀
 * @param[printStream] 日志输出流
 * @param[line] 日志是否换行
 */
@JvmOverloads
fun String.infoLog(prefix: String = "[INFO] [${Date().format()}] ", suffix: String = "", printStream: PrintStream = System.out, line: Boolean = true) = this.log(prefix, suffix, printStream, line)

/**
 * 输出调试级别日志到PrintStream
 *
 * @param[prefix] 日志前缀
 * @param[suffix] 日志后缀
 * @param[printStream] 日志输出流
 * @param[line] 日志是否换行
 */
@JvmOverloads
fun String.debugLog(prefix: String = "[DEBUG] [${Date().format()}] ", suffix: String = "", printStream: PrintStream = System.out, line: Boolean = true) = this.log(prefix, suffix, printStream, line)

/**
 * 输出错误级别日志到PrintStream
 *
 * @param[prefix] 日志前缀
 * @param[suffix] 日志后缀
 * @param[printStream] 日志输出流
 * @param[line] 日志是否换行
 */
@JvmOverloads
fun String.errorLog(prefix: String = "[ERROR] [${Date().format()}] ", suffix: String = "", exitStatus: Int = -1, printStream: PrintStream = System.err, line: Boolean = true) {
    this.log(prefix, suffix, printStream, line)
    if (exitStatus >= 0) exitProcess(exitStatus)
}

// endregion

// region 加密
/**
 * 获取字符串的散列值
 *
 * @param[type] 散列值类型
 */
@JvmOverloads
fun String.hash(type: EHashType = EHashType.SHA256) = this.toByteArray().hash(type)
// endregion

// region URL
/**
 * 对字符串进行URL编码
 *
 * @param[charset] 编码格式
 */
@JvmOverloads
fun String.urlEncode(charset: Charset = StandardCharsets.UTF_8): String = URLEncoder.encode(this, charset)

/**
 * 对字符串进行URL解码
 *
 * @param[charset] 编码格式
 */
@JvmOverloads
fun String.urlDecode(charset: Charset = StandardCharsets.UTF_8): String = URLDecoder.decode(this, charset)
//endregion

/**
 * 字符串是否为null或空
 */
fun String?.isNullOrBlank() = this == null || this.isBlank()

/**
 * 字符串是否不为null也不为空
 */
fun String?.isNotNullOrBlank() = !this.isNullOrBlank()

/**
 * 字符串转Date类型
 *
 * @param[format] 日期格式
 */
@JvmOverloads
fun String.parseDate(format: String = "yyyy-MM-dd"): Date = SimpleDateFormat(format).parse(this)

/**
 * 计算字符串Hash
 *
 * @param[hash] 上次计算的Hash值
 * @param[p] 素数乘数
 * @param[mod] 结果的模数
 */
@JvmOverloads
fun String.getHash(hash: Long = 0, p: Long = 2333, mod: Long = Long.MAX_VALUE): Long {
    var result = hash
    for (c in this)
        result = c.getHash(result, p, mod)
    return result
}

/**
 * 计算字符串Hash
 *
 * @param[hash] 上次计算的Hash值
 * @param[p] 素数乘数
 * @param[mod] 结果的模数
 */
@JvmOverloads
fun String.getHash(hash: Int = 0, p: Int = 2333, mod: Int = 1000000007): Int = this.getHash(hash.toLong(), p.toLong(), mod.toLong()).toInt()

/**
 * 计算字符串双Hash
 *
 * @param[hash] 上次计算的Hash值
 * @param[p] 素数乘数
 * @param[mod1] 结果1的模数
 * @param[mod2] 结果2的模数
 */
@JvmOverloads
fun String.getHash2(hash: LongArray = LongArray(2), p: Long = 2333, mod1: Long = Long.MAX_VALUE, mod2: Long = 2147483647): LongArray {
    hash[0] = this.getHash(hash[0], p, mod1)
    hash[1] = this.getHash(hash[1], p, mod2)
    return hash
}

/**
 * 计算字符串双Hash
 *
 * @param[hash] 上次计算的Hash值
 * @param[p] 素数乘数
 * @param[mod1] 结果1的模数
 * @param[mod2] 结果2的模数
 */
@JvmOverloads
fun String.getHash2(hash: IntArray = IntArray(2), p: Int = 2333, mod1: Int = 1000000007, mod2: Int = 2147483647): IntArray {
    val temp = this.getHash2(longArrayOf(hash[0].toLong(), hash[1].toLong()), p.toLong(), mod1.toLong(), mod2.toLong())
    hash[0] = temp[0].toInt()
    hash[1] = temp[1].toInt()
    return hash
}

/**
 * 执行系统命令，shellPath.executeCommand(..)
 *
 * @sample "/bin/zsh".executeCommand(..)
 *
 * @param[command] 要执行的命令
 * @param[callback] 执行完成后的回调，第一个参数为执行的状态（一般0是正常执行，其他数字为有错误），第二个参数为结果
 * @param[args] 命令需要输入的数据
 * @param[async] 是否异步执行
 * @param[timeout] 超时时间，0为不超时
 * @param[timeUnit] 超时时间的单位
 */
@JvmOverloads
fun String.executeCommand(command: String, callback: ((Int, String) -> Unit)? = null, option: String = "-c", args: String = "", async: Boolean = true, timeout: Long = 0L, timeUnit: TimeUnit = TimeUnit.SECONDS) {
    val func = {
        var state = 1
        val result = StringBuilder()
        try {
            val processBuilder = ProcessBuilder(this, option, command)
            processBuilder.redirectErrorStream(true)
            val process = processBuilder.start()
            if (args.isNotBlank()) {
                val outputStream = process.outputStream
                val outputStreamWriter = OutputStreamWriter(outputStream)
                val bufferedWriter = BufferedWriter(outputStreamWriter)
                bufferedWriter.write(args)
                bufferedWriter.flush()
                closeableClose(bufferedWriter, outputStreamWriter, outputStream)
            }
            if (timeout == 0L) state = process.waitFor()
            else {
                if (process.waitFor(timeout, timeUnit)) state = process.exitValue()
                else {
                    state = 62
                    result.append("执行命令超时")
                }
            }
            val inputStream = process.inputStream
            val inputStreamReader = InputStreamReader(inputStream)
            result.append(inputStreamReader.readText())
            closeableClose(inputStreamReader, inputStream)
        } catch (e: Exception) {
            result.append(e.message)
        }
        if (callback != null) callback(state, result.toString())
    }
    if (async) threadExecute({ func() }) else func()
}
